#pragma once
#include <iostream>
#include <queue>

template <typename T>
class BlockQueue
{
public:
    bool Isfull()
    {
        return _block_queue.size() == _cap;
    }
    BlockQueue(int cap)
        : _cap(cap)
    {
        _consumer_cond_wait = 0;
        _productor_cond_wait = 0;
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_consumer_cond, nullptr);
        pthread_cond_init(&_productor_cond, nullptr);
    }

    void Enqueue(T &data) // 生产者使用
    {
        pthread_mutex_lock(&_mutex);
        while (Isfull()) // 队列为满才进入
        {
            // pthread_cond_wait()的调用：
            // 1.直接让线程等待；
            // 2.让释放原先持有的锁；
            // 3.当条件满足的时候，线程唤醒，pthread_cond_wait也会要求线程去竞争mutex锁，只有成功竞争到了锁，才能成功返回，继续访问临界区
            _productor_cond_wait++;
            pthread_cond_wait(&_productor_cond, &_mutex); // 只要有等待，就一定会有唤醒，如果直接唤醒，那么线程就会直接在没有锁的情况下，访问临界区，所以就有了pthread_cond_wait()的3
            _productor_cond_wait--;
        }
        // 生产
        _block_queue.push(data);

        // 通知消费者
        if (_consumer_cond_wait > 0)
        {
            pthread_cond_signal(&_consumer_cond);
        }
        pthread_mutex_unlock(&_mutex);

        // pthread_cond_signal(&_consumer_cond);所以，这里唤醒线程，在解锁前和解锁后都一样，因为唤醒线程，还是需要等待当前线程解锁了之后，唤醒的线程去竞争到锁才能继续进行下去
        // 所以，即使是pthread_cond_broadcast唤醒一批线程，也是需要竞争锁的，串行访问临界资源
    }

    void Outqueue(T *out) // 消费者使用
    {
        pthread_mutex_lock(&_mutex);

        while (!Isfull()) // 队列为空才进入
        {

            _consumer_cond_wait++;

            pthread_cond_wait(&_consumer_cond, &_mutex);

            _consumer_cond_wait--;

            // 伪唤醒：我们目前的模型还是，生产者生产一个产品，就通知消费者来消费的单生产者和单消费者的简单模型
            // 就是当消费者在队列里有很多产品等着消费，基本不怎么需要等待，但是此时的生产者依旧在不断的通知消费者来消费。
            // 此时，生产者的通知频率远低于消费者的调用频率

            // 伪唤醒：简单的来讲就是我使用pthread_cond_broadcast()去唤醒一批线程，
            // 但是，队列里只有一个产品，第一个消费者消费完了，
            // 由于，线程全都被唤醒了都在竞争锁，不在受到条件变量的约束了，第一个消费完了之后,队列里就没有产品了。
            // 当第一个消费完了之后，锁被释放，其他的线程有去竞争锁，第二个线程去消费的时候就没有产品了，此时发生了错误
            // 解决方法：判断队列是否为空，必须每一次唤醒的时候，判断一下队列是否为空if---->while : 保证代码的健壮性
        }
        *out = _block_queue.front();

        _block_queue.pop();

        if (_productor_cond_wait > 0)
        {
            pthread_cond_signal(&_productor_cond);
        }
        pthread_mutex_unlock(&_mutex);
        // return nums;
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_consumer_cond);
        pthread_cond_destroy(&_productor_cond);
    }

private:
    std::queue<T> _block_queue; // 线程里的全局的临界资源就是阻塞队列，而阻塞队列的临界资源就是队列，
    //                         //阻塞队列里的其他封装的是对于条件变量和锁是对于临界资源的保护
    int _cap;
    pthread_mutex_t _mutex;
    pthread_cond_t _consumer_cond;
    pthread_cond_t _productor_cond;

    int _consumer_cond_wait;
    int _productor_cond_wait;
};